// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_auth.h"

#include <algorithm>

#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "net/base/net_errors.h"
#include "net/http/http_auth_challenge_tokenizer.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_auth_scheme.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"

namespace net {

HttpAuth::Identity::Identity()
    : source(IDENT_SRC_NONE)
    , invalid(true)
{
}

// static
void HttpAuth::ChooseBestChallenge(
    HttpAuthHandlerFactory* http_auth_handler_factory,
    const HttpResponseHeaders& response_headers,
    const SSLInfo& ssl_info,
    Target target,
    const GURL& origin,
    const std::set<Scheme>& disabled_schemes,
    const BoundNetLog& net_log,
    std::unique_ptr<HttpAuthHandler>* handler)
{
    DCHECK(http_auth_handler_factory);
    DCHECK(handler->get() == NULL);

    // Choose the challenge whose authentication handler gives the maximum score.
    std::unique_ptr<HttpAuthHandler> best;
    const std::string header_name = GetChallengeHeaderName(target);
    std::string cur_challenge;
    size_t iter = 0;
    while (response_headers.EnumerateHeader(&iter, header_name, &cur_challenge)) {
        std::unique_ptr<HttpAuthHandler> cur;
        int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
            cur_challenge, target, ssl_info, origin, net_log, &cur);
        if (rv != OK) {
            VLOG(1) << "Unable to create AuthHandler. Status: "
                    << ErrorToString(rv) << " Challenge: " << cur_challenge;
            continue;
        }
        if (cur.get() && (!best.get() || best->score() < cur->score()) && (disabled_schemes.find(cur->auth_scheme()) == disabled_schemes.end()))
            best.swap(cur);
    }
    handler->swap(best);
}

// static
HttpAuth::AuthorizationResult HttpAuth::HandleChallengeResponse(
    HttpAuthHandler* handler,
    const HttpResponseHeaders& response_headers,
    Target target,
    const std::set<Scheme>& disabled_schemes,
    std::string* challenge_used)
{
    DCHECK(handler);
    DCHECK(challenge_used);
    challenge_used->clear();
    HttpAuth::Scheme current_scheme = handler->auth_scheme();
    if (disabled_schemes.find(current_scheme) != disabled_schemes.end())
        return HttpAuth::AUTHORIZATION_RESULT_REJECT;
    std::string current_scheme_name = SchemeToString(current_scheme);
    const std::string header_name = GetChallengeHeaderName(target);
    size_t iter = 0;
    std::string challenge;
    HttpAuth::AuthorizationResult authorization_result = HttpAuth::AUTHORIZATION_RESULT_INVALID;
    while (response_headers.EnumerateHeader(&iter, header_name, &challenge)) {
        HttpAuthChallengeTokenizer props(challenge.begin(), challenge.end());
        if (!base::LowerCaseEqualsASCII(props.scheme(),
                current_scheme_name.c_str()))
            continue;
        authorization_result = handler->HandleAnotherChallenge(&props);
        if (authorization_result != HttpAuth::AUTHORIZATION_RESULT_INVALID) {
            *challenge_used = challenge;
            return authorization_result;
        }
    }
    // Finding no matches is equivalent to rejection.
    return HttpAuth::AUTHORIZATION_RESULT_REJECT;
}

// static
std::string HttpAuth::GetChallengeHeaderName(Target target)
{
    switch (target) {
    case AUTH_PROXY:
        return "Proxy-Authenticate";
    case AUTH_SERVER:
        return "WWW-Authenticate";
    default:
        NOTREACHED();
        return std::string();
    }
}

// static
std::string HttpAuth::GetAuthorizationHeaderName(Target target)
{
    switch (target) {
    case AUTH_PROXY:
        return HttpRequestHeaders::kProxyAuthorization;
    case AUTH_SERVER:
        return HttpRequestHeaders::kAuthorization;
    default:
        NOTREACHED();
        return std::string();
    }
}

// static
std::string HttpAuth::GetAuthTargetString(Target target)
{
    switch (target) {
    case AUTH_PROXY:
        return "proxy";
    case AUTH_SERVER:
        return "server";
    default:
        NOTREACHED();
        return std::string();
    }
}

// static
const char* HttpAuth::SchemeToString(Scheme scheme)
{
    static const char* const kSchemeNames[] = {
        kBasicAuthScheme, kDigestAuthScheme, kNtlmAuthScheme,
        kNegotiateAuthScheme, kSpdyProxyAuthScheme, kMockAuthScheme
    };
    static_assert(arraysize(kSchemeNames) == AUTH_SCHEME_MAX,
        "http auth scheme names incorrect size");
    if (scheme < AUTH_SCHEME_BASIC || scheme >= AUTH_SCHEME_MAX) {
        NOTREACHED();
        return "invalid_scheme";
    }
    return kSchemeNames[scheme];
}

} // namespace net
